# -*- coding: UTF-8 -*-
"""
@Project :xlx-fastapi
@File    :str_util.py
@Author  :xlx-ali
@Date    :2024/06/24 09:18 星期一
"""
import hashlib
import io
import json
import os
import random
import string
import uuid
from datetime import datetime

import requests
import shortuuid
from PIL import Image, ImageDraw, ImageFont

from app import dao
from app.config.config_field import configField


class StringUtil:
    """
    字符串工具类
    """

    @staticmethod
    def GenerateMd5(data: str) -> str:
        """
        生成MD5
        :param data:
        :return:
        """
        md5_hash = hashlib.md5()
        md5_hash.update(data.encode('utf-8'))
        return md5_hash.hexdigest()

    @staticmethod
    def makeMd5Password(md5Password: str, randstr: str, safecode: str = 'xlx-ali'):
        return StringUtil.GenerateMd5(md5Password + randstr + safecode)

    @staticmethod
    def sha3_encrypt(input_string: str, variant=hashlib.sha3_256) -> str:
        """
        使用SHA-3系列哈希算法加密字符串
        variant 参数允许你选择SHA-3的不同变体，默认为SHA3_256
        """
        # 创建一个哈希对象
        sha3_hash = variant()

        # 更新哈希对象的内容
        sha3_hash.update(input_string.encode('utf-8'))

        # 获取16进制的哈希值
        hex_dig = sha3_hash.hexdigest()

        return hex_dig

    @staticmethod
    def generate_captcha_text(length=6):
        """
        生成随机验证码 文本
        """
        return ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(length))

    @staticmethod
    def generate_captcha_image(captcha_text):
        """
        生成验证码图片 图片
        """
        width, height = 120, 40
        background_color = (255, 255, 255)
        text_color = (0, 0, 0)
        font_size = 24
        # 确保字体文件存在，或者使用系统默认字体
        font_path = '/usr/share/fonts/dejavu/DejaVuSans.ttf'  # 字体文件路径，根据实际情况修改

        image = Image.new('RGB', (width, height), color=background_color)
        draw = ImageDraw.Draw(image)
        font = ImageFont.truetype(font_path, font_size)
        draw.text((10, 10), captcha_text, font=font, fill=text_color)

        # 添加噪点
        for _ in range(50):
            draw.point((random.randint(0, width), random.randint(0, height)),
                       fill=(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))

        # 返回图片字节流
        buf = io.BytesIO()
        image.save(buf, format='PNG')
        byte_im = buf.getvalue()
        return byte_im

    @staticmethod
    def format_timestamp_to_str(timestamp, format_str='%Y-%m-%d %H:%M:%S'):
        dt_utc = datetime.fromtimestamp(int(timestamp))
        formatted_date = dt_utc.strftime(format_str)
        return formatted_date

    @staticmethod
    def resetImgName(fileName: str):
        suffix = os.path.splitext(fileName)[1]
        unique_id = uuid.uuid4()
        new_name = StringUtil.GenerateMd5(unique_id.hex) + suffix
        return new_name

    @staticmethod
    def get_client_ipaddress(ip: str):
        if not ip:
            return '未知'
        if ip == '127.0.0.1':
            return '内网IP'
        retaddr = dao.redis_dao.get('ip_addr_' + ip)
        if retaddr:
            return retaddr
        ak = StringUtil.webConfig('map_server_ak')
        if not ak:
            return '未知'
        url = 'https://api.map.baidu.com/location/ip?ak=' + ak + '&ip=' + ip
        response = requests.get(url)
        if response.status_code == 200:
            result = response.json()
            if result['status'] != 0:
                return '未知'
            ret = result['content']['address'] if result['content']['address'] != '' else '未知'
            dao.redis_dao.set('ip_addr_' + ip, ret, 2592000)
            return ret
        else:
            return '未知'

    @staticmethod
    def isJsonString(data: str) -> bool:
        try:
            json.loads(data)
            return True
        except json.JSONDecodeError:
            return False

    @staticmethod
    def webConfig(filed: str) -> str:
        result = dao.ConfigQueryDao.findByName(filed)
        print("webConfig", filed, result)
        return result.value

    @staticmethod
    def webConfigGetCache(name: str = ''):
        data = dao.redis_dao.get('cache_config')
        if not data:
            where = {'is_secret': 0}
            raw_data = dao.ConfigQueryDao.findList(field_names=['name', 'value'], **where)
            data = {}
            for item in raw_data:
                if StringUtil.isJsonString(item['value']):
                    data[item['name']] = json.loads(item['value'])
                else:
                    data[item['name']] = item['value']
            dao.redis_dao.set('cache_config', json.dumps(data))
        else:
            if StringUtil.isJsonString(data):
                data = json.loads(data)
        if name != '':
            data = data[name]
        return data

    @staticmethod
    def getFrontendCache(name: str = ''):
        data = dao.redis_dao.get('cache_config_frontend')
        if not data:
            where = {'is_secret': 1}
            data = dao.ConfigQueryDao.findList(field_names=['name', 'value'], **where)
            for key, value in data.items():
                if StringUtil.isJsonString(value):
                    data[key] = json.loads(value)
            dao.redis_dao.set('cache_config', json.dumps(data))
        if StringUtil.isJsonString(data):
            data = json.loads(data)
        if name != '':
            data = data[name]
        return data

    @staticmethod
    def getCacheAll(name: str = ''):
        data = dao.redis_dao.get('cache_config_all')
        if not data:
            data = dao.ConfigQueryDao.findList(field_names=['name', 'value'])
            for key, value in data.items():
                if StringUtil.isJsonString(value):
                    data[key] = json.loads(value)
            dao.redis_dao.set('cache_config', json.dumps(data))
        if StringUtil.isJsonString(data):
            data = json.loads(data)
        if name != '':
            data = data[name]
        return data

    @classmethod
    def saveConfig(config: dict, adminInfo):
        configfield = configField
        if not configfield:
            raise Exception("缺少配置项文件")
        configList = dao.ConfigQueryDao.findList(field_names=['id', 'name', 'value'])
        if not configList:
            raise Exception("系统配置项为空")
        sqlData = []
        logData = []
        menu_path = ''
        for config_name, config_value in config.items():
            if config_name not in configField:
                continue
            if config_name not in configList:
                continue
            fieldInfo = configField[config_name]
            menu_zh = fieldInfo['menu_zh']
            menu_path = '-'.join(menu_zh)
            configInfo = configList[config_name]
            old_value = configInfo['value']
            if isinstance(config_value, list):  # 检查是否是列表
                new_value = json.dumps(config_value, ensure_ascii=False)  # 类似于JSON_UNESCAPED_UNICODE
            else:
                new_value = config_value  # 如果不是列表，则直接使用原值
            sqlData.append({'id': configInfo['id'], 'name': config_name, 'value': new_value})
            # 是否记录LOG 0:不记录|1:记录
            is_log = fieldInfo.get('is_log', 0)
            # json格式配置项
            field_json = fieldInfo.get('is_json', 0)
            # 数组格式配置项
            field_foreach = fieldInfo.get('is_foreach', 0)
            if new_value != old_value and is_log == 1:
                if field_json:
                    old_arr = json.loads(old_value)
                    new_arr = json.loads(new_value)
                    for config_key, config_value in new_arr.items():
                        if config_key not in field_json:
                            continue
                        field_json_config = field_json[config_key]
                        config_old = old_arr.get(config_key, '未知')
                        # 配置项单位
                        unit = field_json_config.get('unit', '')
                        if 'map' in field_json_config and not field_json_config['map']:
                            config_value = field_json_config.get('map', {}).get(config_value, '未知')
                            config_old = field_json_config.get('map', {}).get(config_old, '未知')
                        logData.append(field_json_config['name'] + '：' + config_old + '->' + config_value + unit)
                elif field_foreach:
                    old_arr = json.loads(old_value)
                    new_arr = json.loads(new_value)
                    old_config = {item[field_foreach['pk']]: item for item in old_arr if field_foreach['pk'] in item}
                    new_config = {item[field_foreach['pk']]: item for item in new_arr if field_foreach['pk'] in item}
                    logChild = []
                    for n_key, n_config in new_config.items():
                        if n_key not in old_config or not old_config[n_key]:
                            continue
                        o_value = old_config[n_key][field_foreach['value']]
                        n_value = n_config[field_foreach['value']]
                        unit = field_foreach.get('unit', '')
                        if n_value != o_value:
                            logChild.append(n_config[field_foreach['name']] + ':' + o_value + '->' + n_value + unit)
                    logData.append(fieldInfo['field_name'] + '：' + ';'.join(logChild))
                else:
                    # 配置项单位
                    unit = fieldInfo.get('unit', '')
                    if 'map' in fieldInfo and fieldInfo['map']:
                        old_value = fieldInfo.get('map', {}).get(old_value, '未知')
                        new_value = fieldInfo.get('map', {}).get(new_value, '未知')
                    if 'correlation' in fieldInfo and fieldInfo['correlation']:
                        correlation = fieldInfo['correlation']
                        # 定义一个通用方法find(查询字段名，判断符，值，过滤的字段名)
                        old_value = correlation['model'].find(correlation['pk'], correlation['condition'], old_value,
                                                              correlation['value'])
                        old_value = correlation['implode'].join(old_value)
                        new_value = correlation['model'].find(correlation['pk'], correlation['condition'], new_value,
                                                              correlation['value'])
                        new_value = correlation['implode'].join(new_value)
                    logData.append(fieldInfo['field_name'] + '：' + old_value + '->' + new_value + unit)
        dao.ConfigOperateDao.updateAll(sqlData)
        # 日志
        log_field = '修改' + menu_path + '，' + '；'.join(logData)
        dao.AdminLogOperateDao.writeLog(log_field, adminInfo, 0, 3)

    @staticmethod
    def mb_rtrim(str_value, char):
        if not str_value:
            return ''

        # 使用encode和decode来处理多字节字符（这里假设是UTF-8编码）
        # 注意：这种方法可能不适用于所有多字节字符集，但它对UTF-8通常是足够的
        str_bytes = str_value.encode('utf-8')
        char_bytes = char.encode('utf-8')

        # 使用while循环来移除末尾的字符，直到不再匹配为止
        while str_bytes and str_bytes.endswith(char_bytes):
            str_bytes = str_bytes[:-len(char_bytes)]

        # 将处理后的字节串转回字符串
        return str_bytes.decode('utf-8')

    @staticmethod
    def get_uniqid(type: str = None):
        """
        根据类型获取唯一标识hex，int，bytes
        :param type:str
        :return:
        """
        unique_id = uuid.uuid4()
        if type == "hex":
            return unique_id.hex
        elif type == "int":
            return unique_id.int
        elif type == "bytes":
            return unique_id.bytes
        else:
            return shortuuid.uuid().isalnum()
